package edu.washington.ling.syntric.util;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

import edu.washington.ling.syntric.struct.error.CollectingExceptionHandler;
import edu.washington.ling.syntric.struct.error.TreeMergeException;
import edu.washington.ling.syntric.struct.error.UnreadableRepresentationException;
import edu.washington.ling.syntric.struct.tree.Node;
import edu.washington.ling.syntric.struct.tree.Token;
import edu.washington.ling.syntric.struct.tree.Tree;

public class MergeUtils {
	
	public static String getSequenceString(List<Token> tokens) {
		ArrayList<String> elements = new ArrayList<String>();
		for (Token t: tokens) {
			if (t.isDSEmptyCategory()) {
				elements.add("NULL_DS");
			} else if (t.isPSEmptyCategory()) {
				String ecType = t.getValue("ectype");
				elements.add(ecType + "_PS");
			} else {
				elements.add(t.getWord());
			}
		}
		return Utils.join(elements, " ");
	}
	public static Tree mergeDSandPSTrees(Tree ds, Tree ps) throws TreeMergeException {
		return mergeDSandPSTrees(ds, ps, true, true);
	}
	public static Tree mergeDSandPSTrees(Tree ds, Tree ps, boolean mergeECsFromDS, boolean mergeECsFromPS) throws TreeMergeException {
		CollectingExceptionHandler cHandler = new CollectingExceptionHandler();
		Tree ds_ps = new Tree("DS+PS");
		Tree ps_copy= TreeUtils.copyTree(ps);
		ds_ps.setPSRoot(ps_copy.getPSRoot());
		List<Token> tokens = mergeDSandPSTokenLists(ds.getTokenList(), ps_copy.getTokenList(), mergeECsFromDS, mergeECsFromPS);
		ds_ps.addAllTokens(tokens);
		try {
			TreeUtils.derriveDSLinksFromFeatures(ds_ps);
			TreeUtils.derrivePBLinksFromFeatures(ds_ps);
		}
		catch (UnreadableRepresentationException e){
			cHandler.handle(e);
			throw new TreeMergeException("Merged representation is unreadable", cHandler.getMessages());
		}
		return ds_ps;
	}

	private static List<Token> mergeDSandPSTokenLists(List<Token> dsTokens, List<Token> psTokens, 
			boolean mergeECsFromDS, boolean mergeECsFromPS) throws TreeMergeException {
		LinkedList<Token> dsTokenQ = new LinkedList<Token>(dsTokens);
		LinkedList<Token> psTokenQ = new LinkedList<Token>(psTokens);
		List<Token> mergedTokens = new LinkedList<Token>();
		int nameIndex = 0;
		while (dsTokenQ.size() > 0 && psTokenQ.size() > 0) {
			Token dsToken = dsTokenQ.peekFirst();
			Token psToken = psTokenQ.peekFirst();
			if (!dsToken.isEmptyCategory() && !psToken.isEmptyCategory()) {
				// try to merge this word token
				if (dsToken.getWord().equals(psToken.getWord())) {
					dsTokenQ.removeFirst();
					psTokenQ.removeFirst();
					mergedTokens.add(createMergedToken(dsToken, psToken));
				}
				else {
					String dsString = MergeUtils.getSequenceString(dsTokens);
					String psString = MergeUtils.getSequenceString(psTokens);
					String errString = String.format("Words %s and %s do not match", dsToken.getWord(), psToken.getWord());
					throw new TreeMergeException(errString, "DS", dsString, "PS", psString);
				}
			}
			else if (dsToken.isEmptyCategory() && psToken.isEmptyCategory() && (mergeECsFromDS || mergeECsFromPS)) {
				// try to merge this empty category token
				if (dsToken.isPSEmptyCategory() && psToken.isPSEmptyCategory()) {
					if (dsToken.getValue("ectype").equals(psToken.getValue("ectype"))) {
						dsTokenQ.removeFirst();
						psTokenQ.removeFirst();
						mergedTokens.add(createMergedToken(dsToken, psToken));
					}
					else {
						String dsString = getSequenceString(dsTokens);
						String psString = getSequenceString(psTokens);
						String errString = String.format("EC types %s and %s do not match", dsToken.getValue("ectype"), psToken.getValue("ectype"));
						throw new TreeMergeException(errString, "DS", dsString, "PS", psString);
					}
				}
				else if (dsToken.isDSEmptyCategory() && psToken.isDSEmptyCategory()) {
					dsTokenQ.removeFirst();
					psTokenQ.removeFirst();
					mergedTokens.add(createMergedToken(dsToken, psToken));
				}
				else {
					String dsString = getSequenceString(dsTokens);
					String psString = getSequenceString(psTokens);
					String errString = String.format("ECs not match");
					throw new TreeMergeException(errString, "DS", dsString, "PS", psString);
				}
			}
			else if (dsToken.isEmptyCategory() && mergeECsFromDS == false) {
				mergedTokens.add(dsToken);
				dsTokenQ.removeFirst();
			}
			else if (psToken.isEmptyCategory() && mergeECsFromPS == false) {
				mergedTokens.add(psToken);
				psToken.setName(psToken.getValue("ectype") + nameIndex);
				nameIndex++;
				psTokenQ.removeFirst();
			}
			else {
				String dsString = MergeUtils.getSequenceString(dsTokens);
				String pbString = MergeUtils.getSequenceString(psTokens);
				throw new TreeMergeException("Token merge error", "DS", dsString, "PS", pbString);
			}
		}
		if (dsTokenQ.size() > 0 || psTokenQ.size() > 0) {
			String dsString = MergeUtils.getSequenceString(dsTokens);
			String psString = MergeUtils.getSequenceString(psTokens);
			throw new TreeMergeException("Token merge error", "DS", dsString, "PS", psString);
		}
		return mergedTokens;
	}
	private static Token createMergedToken(Token dsToken, Token psToken) {
		Token merged = new Token(dsToken);
		Node psParent = psToken.getPSParent();
		psParent.delPSChild(psToken);
		psParent.addPSChild(merged);
		merged.setPSParent(psParent);
		return merged;
	}
}
